
#include <cstdarg>
#include "commandTable-default.h"
#include "world.h"



World::World( void ) {
  booted( time( NULL ) );
  exists( true );
  copyover( false );
  schedule( new TmrTick() );
  schedule( new TmrAutosave() );
  return;
}

World::~World( void ) {
  return;
}

/************************************************************ CONTROL ************************************************************/
void World::startup( void ) { // public

  if ( !load() ) {
    worldLog( FATAL, LOG_WORLD, "There was a problem loading a world component." );
  }

  exist();

  return;
}

void World::exist( const unsigned int& fd ) { // public
  try {
    if ( copyover() ) {
      getServer()->setFd( fd );
    } else {
      getServer()->startup( 6501 );
    }
  } catch ( SocketException se ) {
    worldLog( FATAL, LOG_SYSTEM, "Couldn't start Socket server: %s", se.getError().c_str() );
    exit( EXIT_FAILED_BOOT );
  }

  if ( copyover() ) {
    worldLog( SYSTEM, LOG_SYSTEM, "System rebooted successfully." );
  } else {
    worldLog( SYSTEM, LOG_SYSTEM, "System up on port %d.", getServer()->getPort() );
  }

  while ( exists() ) {
    turn();
  }

  if ( !save() ) {
    worldLog( FATAL, LOG_WORLD, "There was a problem saving a world component." );
  }

  worldLog( SYSTEM, LOG_SYSTEM, "System shutting down." );

  return;
}

void World::turn( void ) { // public
  std::map<std::string,Avatar*>::iterator a_it, next, foo;
  std::vector<Socket*> new_connections;
  std::vector<Socket*>::iterator sock_it;
  Avatar* avatar = NULL;

  /*
   * Deal with new connections.
   */
  new_connections = getServer()->accept();
  for ( sock_it = new_connections.begin(); new_connections.size(); ) {
    insert( ( avatar = new Avatar( (*sock_it) ) ) );
    avatar->pushHandler( new SendBannerHandler( avatar ) );
    worldLog( NOTICE, LOG_SYSTEM, "New connection (fd %d) from %s!", avatar->socket()->getFd(), avatar->socket()->getIP().c_str() );
    new_connections.erase( sock_it );
  }

  // Updated everything...
  updateTimers();
  handleInput();
  handleOutput();
  handleDisconnects();

  // Throttle CPU usage (for now)...
  usleep( 500 );

  return;
}

bool World::reboot( Creature* creature ) { // public
  std::map<std::string,Avatar*>::iterator it;
  estring fd;
  FILE* fp = NULL;

  fp = fopen( REBOOT_FILE, "w" );

  if ( !fp ) {
    if ( creature ) {
      creature->send( "Copyover file couldn't be opened for writing! Copyover aborted!" );
    }
    worldLog( ERROR, LOG_SYSTEM, "Copyover file couldn't be opened for writing! Copyover aborted!" );
    return false;
  }

  for ( it = getAvatars().begin(); it != getAvatars().end(); ++it ) {
    if ( it->second->status() == CONNECTED ) {
      fprintf( fp, "%d %s %s\n", it->second->socket()->getFd(), it->second->socket()->getIP().c_str(), it->second->name().c_str() );
    }
  }

  fclose( fp );

  broadcast( "Hold on to your pants; Copyover in progress..." );
  handleOutput();

  if ( !save() ) {
    worldLog( FATAL, LOG_WORLD, "There was a problem saving a world component." );
    broadcast( "Copyover aborted." );
    return false;
  }

  worldLog( SYSTEM, LOG_SYSTEM, "System going down for reboot." );

  fd = estring( "-fd=" ).append( estring( World::Instance().getServer()->getFd() ) );
  if ( execl( "./symphony", "-copyover", fd.c_str(), NULL ) < 0 ) {
    worldLog( ERROR, LOG_SYSTEM, "execl(): %s", strerror( errno ) );
    broadcast( "Copyover failed." );
  }

  return false;
}

void World::recover( const unsigned int& fd ) { // public
  FILE* fp = NULL;
  int client_fd = 0;
  char ip[MAX_BUFFER];
  char name[MAX_BUFFER];
  Avatar* avatar = NULL;

  if ( !load() ) {
    worldLog( FATAL, LOG_WORLD, "There was a problem loading a world component." );
  }

  fp = fopen( REBOOT_FILE, "r" );

  if ( !fp ) {
    worldLog( FATAL, LOG_SYSTEM, "Copyover file couldn't be opened for reading. Copyover failed." );
    exit( EXIT_FAILED_REBOOT );
  }

  while ( 1 ) {
    if ( fscanf( fp, "%d %s %s", &client_fd, ip, name ) < 3 ) {
      break;
    }
    avatar = new Avatar( new Socket( client_fd ) );
    avatar->socket()->setIP( ip );
    avatar->name( name );
    avatar->load();
    avatar->status( CONNECTED );
    avatar->pushHandler( new InputHandler( avatar ) );
    World::Instance().insert( avatar );
  }

  fclose( fp );
  unlink( REBOOT_FILE );

  broadcast( "Copyover successful." );

  exist( fd );

  return;
}

bool World::load( void ) { // public
  bool status = true;

  if ( !loadAreas() ) {
    status = false;
  }

  loadPermissions();

  return status;
}

bool World::save( void ) { // public
  bool status = true;

  if ( !saveAreas() ) {
    status = false;
  }

  for ( std::map<std::string,Avatar*>::iterator a_it = getAvatars().begin(); a_it != getAvatars().end(); ++a_it ) {
    a_it->second->save();
  }

  worldLog( SYSTEM, LOG_WORLD, "World::save()" );
  World::Instance().bigBrother( NULL, ADMIN_BIGBRO_CHANGES, "World saved." );

  return status;
}

/************************************************************ I/O ************************************************************/
void World::handleInput( void ) { // public
  std::map<std::string,Avatar*>::iterator next;
  for ( std::map<std::string,Avatar*>::iterator a_it = getAvatars().begin(); a_it != getAvatars().end(); ) {
    next = a_it;
    next++;
    if ( a_it->second->hasInput() ) {
      a_it->second->handle();
    }
    a_it = next;
  }
  return;
}

void World::handleOutput( void ) { // public
  std::map<std::string,Avatar*>::iterator next;
  for ( std::map<std::string,Avatar*>::iterator a_it = getAvatars().begin(); a_it != getAvatars().end(); ++a_it ) {
    if ( a_it->second->hasOutput() ) {
      a_it->second->flushOutput();
    }
  }
  return;
}

void World::handleDisconnects( void ) { // public
  std::map<std::string,Avatar*>::iterator next;
  for ( std::map<std::string,Avatar*>::iterator a_it = getAvatars().begin(); a_it != getAvatars().end(); ) {
    if ( a_it->second->disconnected() ) {
      /* We have to go through a bit of a song and dance here, because map::erase() does
       * not return the next valid iterator in the sequence like vector::erase() does.
       */
      next = a_it;
      ++next;
      a_it->second->save();
      a_it->second->room()->remove( a_it->second );
      delete a_it->second;
      getAvatars().erase( a_it );
      a_it = next;
    } else {
      ++a_it;
    }
  }
  return;
}

void World::broadcast( const std::string& message ) { // public
  for ( std::map<std::string,Avatar*>::iterator it = getAvatars().begin(); it != getAvatars().end(); it++ ) {
    if ( it->second->status() == CONNECTED ) {
      it->second->send( message );
      it->second->send( "\n" );
    }
  }
  return;
}

void World::bigBrother( Creature* creature, const unsigned long& type, const char* format, ... ) { // public
  char buffer[MAX_BUFFER];
  va_list args;

  va_start( args, format );
  vsprintf( buffer, format, args );
  va_end( args );

  if ( creature && type ) {
    for ( std::map<std::string,Avatar*>::iterator it = World::Instance().getAvatars().begin(); it != World::Instance().getAvatars().end(); ++it ) {
      if ( it->second->admin( ADMIN_BIGBRO ) && it->second->admin( type ) && it->second->level() >= creature->level() && it->second != creature ) {
        it->second->send( "BigBrother: %s", buffer );
      }
    }
  } else if ( type ) {
    for ( std::map<std::string,Avatar*>::iterator it = World::Instance().getAvatars().begin(); it != World::Instance().getAvatars().end(); ++it ) {
      if ( it->second->admin( ADMIN_BIGBRO ) && it->second->admin( type ) ) {
        it->second->send( "BigBrother: %s", buffer );
      }
    }
  } else {
    for ( std::map<std::string,Avatar*>::iterator it = World::Instance().getAvatars().begin(); it != World::Instance().getAvatars().end(); ++it ) {
      if ( it->second->admin( ADMIN_BIGBRO ) ) {
        it->second->send( "BigBrother: %s", buffer );
      }
    }
  }

  return;
}

/************************************************************ EVENTS ************************************************************/
void World::schedule( Timer* timer ) { // public
  getTimers().push_back( timer );
  return;
}

void World::unschedule( Timer* timer ) { // public
  timer->enabled( false );
  return;
}

void World::updateTimers( void ) { // public
  std::vector<Timer*>::iterator it;
  Timer* timer = NULL;

  for ( it = getTimers().begin(); it != getTimers().end(); ++it ) {
    timer = *it;
    if ( timer->enabled() && timer->ready() ) {
      timer->fire();
    }
  }

  return;
}

/************************************************************ CREATURES ************************************************************/
void World::insert( Creature* creature ) { // public
  getCreatures().insert( creature );
  if ( creature->type( TYPE_AVATAR ) ) {
    getAvatars().insert( std::make_pair( creature->name(), (Avatar*)creature ) );
  }
  return;
}

void World::remove( Creature* creature ) { // public
  getCreatures().erase( creature );
  if ( creature->type( TYPE_AVATAR ) ) {
    getAvatars().erase( creature->name() );
  }
  return;
}

Creature* World::findCreature( const std::string& name ) { // public
  for ( std::set<Creature*>::iterator it = getCreatures().begin(); it != getCreatures().end(); ++it ) {
    if ( (*it)->matchesKeyword( name ) ) {
      return *it;
    }
  }
  return NULL;
}

/* Avatar-specific search method (slightly more efficient when searching only for PCs) */
Avatar* World::findAvatar( const std::string& name ) { // public
  for ( std::map<std::string,Avatar*>::iterator it = getAvatars().begin(); it != getAvatars().end(); ++it ) {
    if ( it->second->matchesKeyword( name ) ) {
      return it->second;
    }
  }
  return NULL;
}

bool World::transport( Creature* creature, const unsigned long& vnum ) { // public
  Room* room = NULL;
  CmdLook look;

  if ( ( room = findRoom( vnum ) ) == NULL ) {
    return false;
  }

  creature->room()->remove( creature );
  room->add( creature );
  creature->room( room );
  creature->send( "You've been transported to %s.\n", creature->room()->name().c_str() );
  look.execute( creature, "" );
  return true;
}

/************************************************************ AVATARS ************************************************************/
Avatar* World::findAvatar( const unsigned long& ID ) { // public
  for ( std::map<std::string,Avatar*>::iterator it = getAvatars().begin(); it != getAvatars().end(); ++it ) {
    if ( it->second->type( TYPE_AVATAR ) && it->second->ID() == ID ) {
      return it->second;
    }
  }
  return NULL;
}

std::string World::getAvatarNameByID( const unsigned long& ID ) { // public
  try {
    ROW row;
    char query[MAX_BUFFER];

    sprintf( query,
      " SELECT name           \
        FROM avatars          \
        WHERE avatarID = %lu  \
        LIMIT 1;",
      ID
    );
    if ( World::Instance().getMysql()->select( query ) ) {
      if ( row = World::Instance().getMysql()->fetch() ) {
        return row["name"];
      }
    }
    return std::string("empty");

  } catch ( MysqlException me ) {
    fprintf( stderr, "Failed to save area %lu: %s\n", ID, me.getMessage().c_str() );
    return std::string("error");
  }
}

bool World::removeAvatar( const std::string& name ) { // public
  for ( std::map<std::string,Avatar*>::iterator it = getAvatars().begin(); it != getAvatars().end(); ++it ) {
    if ( it->first == name ) {
      getCreatures().erase( it->second );
      getAvatars().erase( name );
      return true;
    }
  }
  return false;
}

/************************************************************ AREAS ************************************************************/
bool World::loadAreas( void ) { // public
  Area* area = NULL;
  ROW row;

  // Grab areas...
  try {
    if ( getMysql()->select( "SELECT * FROM areas ORDER BY low ASC;" ) ) {
      while ( row = getMysql()->fetch() ) {
        area = new Area();
        area->load( row );
        insert( area );
      }
    }
  } catch ( MysqlException me ) {
    fprintf( stderr, "Failed to load areas: %s\n", me.getMessage().c_str() );
    return false;
  }

  // Load World components...
  for ( std::set<Area*,area_comp>::iterator it = getAreas().begin(); it != getAreas().end(); ++it ) {
    (*it)->loadRooms();
    (*it)->loadExits();
    (*it)->loadObjects();
    (*it)->loadMobs();
  }

  return true;
}

bool World::saveAreas( void ) { // public
  bool status = true;
  for ( std::set<Area*,area_comp>::iterator it = getAreas().begin(); it != getAreas().end(); ++it ) {
    if ( !(*it)->save() ) {
      status = false;
    }
  }
  return status;
}

void World::insert( Area* area ) { // public
  getAreas().insert( area );
  return;
}

void World::remove( Area* area ) { // public
  getAreas().erase( area );
  return;
}

Area* World::findArea( const unsigned long& ID ) { // public
  for ( std::set<Area*,area_comp>::iterator it = getAreas().begin(); it != getAreas().end(); ++it ) {
    if ( (*it)->ID() == ID ) {
      return *it;
    }
  }
  return NULL;
}

Area* World::lookup( const unsigned long& vnum ) { // public
  for ( std::set<Area*,area_comp>::iterator it = getAreas().begin(); it != getAreas().end(); ++it ) {
    if ( (*it)->low() <= vnum && vnum <= (*it)->high() ) {
      return *it;
    }
  }
  return NULL;
}

/************************************************************ AREA PERMISSIONS ************************************************************/
bool World::hasPermission( Area* area, Avatar* avatar ) { // public
  // Only the Administrator can fiddle with Limbo...
  if ( area->ID() == 1 && avatar->level() < CREATOR ) {
    return false;
  }
  // The Administrator and the Head Builder can edit anything else...
  if ( avatar->admin( ADMIN_HEAD_BUILDER ) || avatar->level() >= CREATOR ) {
    return true;
  }
  // The average Joe needs explicit permission for a given area...
  if ( permissions().find( std::make_pair( area->ID(), avatar->ID() ) ) != permissions().end() ) {
    return true;
  }
  return false;
}

void World::givePermission( Area* area, Avatar* avatar ) { // public
  permissions().insert( std::make_pair( area->ID(), avatar->ID() ) );
  try {
    char query[MAX_BUFFER];
    sprintf( query, "INSERT IGNORE INTO permissions ( areaID, avatarID ) VALUES ( %lu, %lu );", area->ID(), avatar->ID() );
    World::Instance().getMysql()->insert( query );
  } catch ( MysqlException me ) {
    fprintf( stderr, "Failed to create area permission: %s\n", me.getMessage().c_str() );
  }
  return;
}

void World::removePermission( Area* area, Avatar* avatar ) { // public
  permissions().erase( std::make_pair( area->ID(), avatar->ID() ) );
  try {
    char query[MAX_BUFFER];
    sprintf( query, "DELETE FROM permissions WHERE areaID = %lu AND avatarID = %lu;", area->ID(), avatar->ID() );
    World::Instance().getMysql()->remove( query );
  } catch ( MysqlException me ) {
    fprintf( stderr, "Failed to create area permission: %s\n", me.getMessage().c_str() );
  }
  return;
}

std::vector<Avatar*> World::getPermissions( Area* area ) { // public
  std::vector<Avatar*> avatars;
  Avatar* avatar = NULL;

  for ( std::set<std::pair<unsigned long,unsigned long> >::iterator it = permissions().begin(); it != permissions().end(); ++it ) {
    if ( it->first == area->ID() ) {
      if ( ( avatar = findAvatar( it->second ) ) != NULL ) {
        avatars.push_back( avatar );
      }
    }
  }

  return avatars;
}

std::vector<Area*> World::getPermissions( Avatar* avatar ) { // public
  std::vector<Area*> areas;
  Area* area = NULL;

  for ( std::set<std::pair<unsigned long,unsigned long> >::iterator it = permissions().begin(); it != permissions().end(); ++it ) {
    if ( it->second == avatar->ID() ) {
      if ( ( area = findArea( it->first ) ) != NULL ) {
        areas.push_back( area );
      }
    }
  }

  return areas;
}

void World::loadPermissions( void ) { // public
  try {
    Mysql* mysql = World::Instance().getMysql();
    ROW row;
    if ( mysql->select( "SELECT * FROM permissions;" ) ) {
      while ( row = mysql->fetch() ) {
        permissions().insert( std::make_pair( row["areaID"], row["avatarID"] ) );
      }
    }
  } catch ( MysqlException me ) {
    fprintf( stderr, "Failed to load area permissions: %s\n", me.getMessage().c_str() );
  }
  return;
}

/************************************************************ ROOMS ************************************************************/
Room* World::findRoom( const unsigned long& vnum ) { // public
  std::map<unsigned long,Room*>::iterator r_it;
  for ( std::set<Area*,area_comp>::iterator a_it = getAreas().begin(); a_it != getAreas().end(); ++a_it ) {
    if ( ( r_it = (*a_it)->rooms().find( vnum ) ) != (*a_it)->rooms().end() ) {
      return r_it->second;
    }
  }
  return NULL;
}

/************************************************************ OBJECTS ************************************************************/
void World::insert( Object* object ) { // public
  getObjects().insert( object );
  return;
}

void World::remove( Object* object ) { // public
  getObjects().erase( object );
  return;
}

Object* World::findObject( const std::string& name ) { // public
  std::set<Object*>::iterator it;
  std::set<Object*>::iterator end = getObjects().end();
  for ( it = getObjects().begin(); it != end; ++it ) {
    if ( (*it)->matchesKeyword( name ) ) {
      return *it;
    }
  }
  return NULL;
}

/************************************************************ STATIC ************************************************************/
void World::worldLog( const unsigned short& level, const unsigned short& type, const char* format, ... ) { // public static
  char buffer[MAX_BUFFER];
  char query[MAX_BUFFER];
  va_list args;

  // Process our arguments...
  va_start( args, format );
  vsprintf( buffer, format, args );
  va_end( args );

  // Prep and send our query...
  sprintf( query, "INSERT INTO world_log ( level, type, text ) VALUES ( %d, %d, '%s' );", level, type, Mysql::addslashes(buffer).c_str() );

  try {
    World::Instance().getMysql()->insert( query );
  } catch ( MysqlException me ) {
    fprintf( stderr, "Failed to log world event: %s\n", query );
  }

  // Depending on the level (and the current system log level) we may also print the message...
  if ( level == SYSTEM || level >= WARNING ) {
    Instance().bigBrother( NULL, ADMIN_BIGBRO_SYSTEM, buffer );
    fprintf( stderr, "Log %s: %s\n", strnow().c_str(), buffer );
  }

  return;
}

void World::playerLog( const unsigned short& level, const unsigned short& type, const char* format, ... ) { // public static
  char buffer[MAX_BUFFER];
  char query[MAX_BUFFER];
  va_list args;

  // Process our arguments...
  va_start( args, format );
  vsprintf( buffer, format, args );
  va_end( args );

  // Prep and send our query...
  sprintf( query, "INSERT INTO player_log ( level, type, text ) VALUES ( %d, %d, '%s' );", level, type, Mysql::addslashes(buffer).c_str() );

  try {
    World::Instance().getMysql()->insert( query );
  } catch ( MysqlException me ) {
    fprintf( stderr, "Failed to log player event: %s\n", query );
  }

  return;
}

unsigned long World::now( void ) { // public static
  return time( NULL );
}

std::string World::strnow( void ) { // public static
  char buffer[MAX_BUFFER];
  time_t seconds = time( NULL );
  tm* info = localtime( &seconds );
  strftime( buffer, MAX_BUFFER, "%Y/%m/%d %H:%M:%S", info );
  return buffer;
}

std::string World::strtime( time_t in_time ) { // public static
  char buffer[MAX_BUFFER];
  time_t seconds = 0;
  if ( in_time ) {
    seconds = in_time;
  } else {
    seconds = time( NULL );
  }
  tm* info = localtime( &seconds );
  strftime( buffer, MAX_BUFFER, "%A, %B %d, %Y  %H:%M:%S", info );
  return buffer;
}

std::string World::realtime( const unsigned long& seconds, unsigned short granularity ) { // public static
  char buffer[MAX_BUFFER];
  char foo[100];
  register unsigned long sec = seconds;
  register unsigned long min = 0;
  register unsigned long hour = 0;
  register unsigned long day = 0;
  register unsigned long year = 0;

  memset( buffer, 0, MAX_BUFFER );

  while ( sec >= 60 ) {
    ++min;
    sec -= 60;
  }

  if ( granularity > GRAN_MINUTE ) {
    while ( min >= 60 ) {
      ++hour;
      min -= 60;
    }
    if ( granularity > GRAN_HOUR ) {
      while ( hour >= 24 ) {
        ++day;
        hour -= 24;
      }
      if ( granularity > GRAN_DAY ) {
        while ( day >= 365 ) {
          ++year;
          day -= 365;
        }
      }
    }
  }

  if ( year ) {
    if ( year == 1 ) {
      sprintf( foo, "%lu year", year );
    } else {
      sprintf( foo, "%lu years", year );
    }
    strcat( buffer, foo );
  }

  if ( day ) {
    if ( day == 1 ) {
      sprintf( foo, "%lu day", day );
    } else {
      sprintf( foo, "%lu days", day );
    }
    if ( year ) {
      strcat( buffer, ", " );
    }
    strcat( buffer, foo );
  }

  if ( hour ) {
    if ( hour == 1 ) {
      sprintf( foo, "%lu hour", hour );
    } else {
      sprintf( foo, "%lu hours", hour );
    }
    if ( year || day ) {
      strcat( buffer, ", " );
    }
    strcat( buffer, foo );
  }

  if ( min ) {
    if ( min == 1 ) {
      sprintf( foo, "%lu minute", min );
    } else {
      sprintf( foo, "%lu minutes", min );
    }
    if ( year || day || hour ) {
      strcat( buffer, ", " );
    }
    strcat( buffer, foo );
  }

  if ( sec ) {
    if ( sec == 1 ) {
      sprintf( foo, "%lu second", sec );
    } else {
      sprintf( foo, "%lu seconds", sec );
    }
    if ( year || day || hour || min ) {
      strcat( buffer, ", " );
    }
    strcat( buffer, foo );
  }

  return buffer;
}

bool World::search_map( Creature* creature, Room*** map, const unsigned short& ymax, const unsigned short& xmax, const short& y, const short& x, Room* room, char** display ) { // public static
  Exit* exit = NULL;
  short new_x = 0;
  short new_y = 0;
  short dis_x = 0;
  short dis_y = 0;
  char dis_c = 0;
  // Bounds check...
  if ( y < 0 || y > ymax*2 || x < 0 || x > xmax*2 ) {
    return true;
  }
  // There's a room at this location...
  if ( map[y][x] ) {
    // Collision check...
    if ( map[y][x] != room ) {
      return false;
    }
    // We've already been here; bail out...
    return true;
  }
  // Store our pointer and map position...
  map[y][x] = room;
  display[y*2][x*2] = get_marker( creature, room );
  // Search from here...
  for ( register unsigned short u = 0; u < 4; ++u ) {
    new_x = x;
    new_y = y;
    switch (u) {
      case NORTH: new_y++; dis_x = new_x*2;   dis_y = new_y*2-1;  dis_c = '|'; break;
      case EAST:  new_x++; dis_x = new_x*2-1; dis_y = new_y*2;    dis_c = '-'; break;
      case SOUTH: new_y--; dis_x = new_x*2;   dis_y = new_y*2+1;  dis_c = '|'; break;
      case WEST:  new_x--; dis_x = new_x*2+1; dis_y = new_y*2;    dis_c = '-'; break;
      default: return false;
    }
    if ( ( exit = room->exit( u ) ) && new_y >= 0 && new_x >= 0 ) {
      if ( exit->flag( EFLAG_HIDDEN ) || exit->target()->flag( RFLAG_NOMAP ) ) {
        continue;
      }
      if ( !World::search_map( creature, map, ymax, xmax, new_y, new_x, room->exit( u )->target(), display ) ) {
        return false;
      }
      display[dis_y][dis_x] = dis_c;
    }
  }
  return true;
}

char World::get_marker( Creature* creature, Room* room ) { // public static

  if ( creature->room() == room ) {
    return '@';
  }

  return '+';
}

std::string World::interpret_map_line( const char* line ) { // public static
  std::string dest;

  for ( unsigned u = 0; u < strlen( line ); ++u ) {
    switch ( line[u] ) {
      case '@': dest.append( "{R@{x" ); break;
      case '|': dest.append( "{c|{x" ); break;
      case '-': dest.append( "{c-{x" ); break;
      default:  dest.append( 1, line[u] );
    }
  }

  return dest;
}

